package trading.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

public class MarketTimeHelper {

    private String expirationPattern;
    private TimeZone timeZone;
    private List<Calendar> stockHolidays;
    private List<Calendar> stockTradingdayWithEarlyEnd;

    public MarketTimeHelper() throws ParseException {
        this.expirationPattern = "yyyyMM";
        this.timeZone = TimeZone.getTimeZone("America/New_York");

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMMM d, yyyy");
        simpleDateFormat.setTimeZone(this.timeZone);

        String[] holidayStrings = { "February 20, 2012", "April 6, 2012",
                "May 28, 2012", "July 4, 2012", "September 3, 2012",
                "November 22, 2012", "December 25, 2012" };

        this.stockHolidays = new ArrayList<Calendar>();
        for (String holidayString : holidayStrings) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(simpleDateFormat.parse(holidayString));
            this.stockHolidays.add(calendar);
        }

        String[] earlyEndDayStrings = { "July 3, 2012", "November 23, 2012",
                "December 24, 2012" };

        this.stockTradingdayWithEarlyEnd = new ArrayList<Calendar>();
        for (String earlyEndDayString : earlyEndDayStrings) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(simpleDateFormat.parse(earlyEndDayString));
            this.stockTradingdayWithEarlyEnd.add(calendar);
        }
    }

    /**
     * Get previous trading day closing time (precisely at 4 pm)
     * 
     * @return
     */
    public Date getLastClosingDateObject() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(this.timeZone);
        while (true) {
            Calendar closingTime = getClosingTime(calendar);
            if (!isTradingDay(calendar) || calendar.before(closingTime)) {
                calendar.add(Calendar.DATE, -1);
                calendar.set(Calendar.HOUR_OF_DAY, 17);
                continue;
            }
            if (calendar.after(closingTime)) {
                return closingTime.getTime();
            }
        }
    }

    /**
     * Return true if it's Mon-Fri.
     * 
     * We should exculde public holidays as well.
     * 
     * @param calendar
     * @return
     */
    private boolean isTradingDay(Calendar calendar) {
        return calendar.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY
                && calendar.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY
                && !isStockMarketHoliday(calendar);
    }

    private boolean isStockMarketHoliday(Calendar calendar) {
        for (Calendar holiday : this.stockHolidays) {
            if (holiday.get(Calendar.YEAR) == calendar.get(Calendar.YEAR)
                    && holiday.get(Calendar.MONTH) == calendar
                            .get(Calendar.MONTH)
                    && holiday.get(Calendar.DAY_OF_MONTH) == calendar
                            .get(Calendar.DAY_OF_MONTH)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get given calendar day's closing time.
     * 
     * We have to set it to 1pm for certain days.
     * 
     * @param calendar
     * @return
     */
    private Calendar getClosingTime(Calendar calendar) {
        Calendar closingTimeToday = (Calendar) calendar.clone();
        closingTimeToday.setTimeZone(this.timeZone);

        // This stub step is needed to update the calendar
        closingTimeToday.get(Calendar.DATE);
        if (this.isStockEarlyEndDay(closingTimeToday)) {
            closingTimeToday.set(Calendar.HOUR_OF_DAY, 13);
        } else {
            closingTimeToday.set(Calendar.HOUR_OF_DAY, 16);
        }
        closingTimeToday.set(Calendar.MINUTE, 0);
        closingTimeToday.set(Calendar.SECOND, 0);
        closingTimeToday.set(Calendar.MILLISECOND, 0);
        return closingTimeToday;
    }

    private boolean isStockEarlyEndDay(Calendar calendar) {
        for (Calendar stockEarlyEndDay : this.stockTradingdayWithEarlyEnd) {
            if (stockEarlyEndDay.get(Calendar.YEAR) == calendar
                    .get(Calendar.YEAR)
                    && stockEarlyEndDay.get(Calendar.MONTH) == calendar
                            .get(Calendar.MONTH)
                    && stockEarlyEndDay.get(Calendar.DAY_OF_MONTH) == calendar
                            .get(Calendar.DAY_OF_MONTH)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get given calendar day's opening time.
     * 
     * @param calendar
     * @return
     */
    private Calendar getOpeningTime(Calendar calendar) {
        Calendar openingTimeToday = (Calendar) calendar.clone();
        // This stub step is needed to update the calendar
        openingTimeToday.get(Calendar.DATE);

        openingTimeToday.set(Calendar.HOUR_OF_DAY, 9);
        openingTimeToday.set(Calendar.MINUTE, 30);
        openingTimeToday.set(Calendar.SECOND, 0);
        openingTimeToday.set(Calendar.MILLISECOND, 0);
        return openingTimeToday;
    }

    public Calendar getNextMarketOpeningTime() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(this.timeZone);
        while (true) {
            Calendar openingTime = getOpeningTime(calendar);
            if (!isTradingDay(calendar) || calendar.after(openingTime)) {
                calendar.add(Calendar.DATE, 1);
                calendar.set(Calendar.HOUR_OF_DAY, 8);
                continue;
            }
            if (calendar.before(openingTime)) {
                return openingTime;
            }
        }
    }

    public Calendar getNextMarketClosingTime() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(this.timeZone);
        while (true) {
            Calendar closingTime = getClosingTime(calendar);
            if (!isTradingDay(calendar) || calendar.after(closingTime)) {
                calendar.add(Calendar.DATE, 1);
                calendar.set(Calendar.HOUR_OF_DAY, 8);
                continue;
            }
            if (calendar.before(closingTime)) {
                return closingTime;
            }
        }
    }

    public Calendar getAfterMarketHourTaskTime() {
        Calendar closingTime = getNextMarketClosingTime();
        // Calendar tomorrow = (Calendar) closingTime.clone();
        // tomorrow.add(Calendar.DATE, 1);

        Calendar taskTime = (Calendar) closingTime.clone();
        //
        // if (isTradingDay(tomorrow)) {
        // taskTime.add(Calendar.HOUR, 6);
        // } else {
        // while (!isTradingDay(tomorrow)) {
        // tomorrow.add(Calendar.DATE, 1);
        // }
        // taskTime = (Calendar) tomorrow.clone();
        // taskTime.add(Calendar.DATE, -1);
        // taskTime.add(Calendar.HOUR, 6);
        // }
        taskTime.add(Calendar.HOUR, 1);
        return taskTime;
    }

    public String getThisMonthExpiration() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(this.timeZone);
        SimpleDateFormat formatter = this
                .getSimpleDateFormatter(this.expirationPattern);
        return formatter.format(calendar.getTime());
    }

    public String getNextMonthExpiration() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(this.timeZone);
        calendar.add(Calendar.MONTH, 1);
        SimpleDateFormat formatter = this
                .getSimpleDateFormatter(this.expirationPattern);
        return formatter.format(calendar.getTime());
    }

    public String getNextNextMonthExpiration() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(this.timeZone);
        calendar.add(Calendar.MONTH, 2);
        SimpleDateFormat formatter = this
                .getSimpleDateFormatter(this.expirationPattern);
        return formatter.format(calendar.getTime());
    }

    public SimpleDateFormat getSimpleDateFormatter(String pattern) {
        SimpleDateFormat formatter = new SimpleDateFormat(pattern);
        formatter.setTimeZone(this.timeZone);
        return formatter;
    }
    
    public SimpleDateFormat getSimpleDateFormatterGMT(String pattern) {
        SimpleDateFormat formatter = new SimpleDateFormat(pattern);
        formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
        return formatter;
    }
    
    public long getDelay(Calendar calendar) {
        long currentTime = Calendar.getInstance().getTimeInMillis();
        return calendar.getTimeInMillis() - currentTime;
        
    }
}
